package cn.mong.util.modelexcel;

import java.awt.Point;
import java.awt.Rectangle;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.poi.xssf.usermodel.XSSFCellStyle;

import cn.hutool.core.bean.BeanUtil;
import cn.mong.util.modelexcel.ModelToCellsConverter.DefaultModelToCellsConverter;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 注解驱动的数据模型excel写入器
 * 
 * @author mengxianming@2021-9-15 9:48:12
 *
 * @param <D>
 */
@Slf4j
public class HeaderAnnotationModelWriter<D> extends AbstractModelWriter<D>{
	
	private static final HeaderCustomInfo EMPTYCELL = new HeaderCustomInfo();

    protected AnnotationModelHeaderResolver headerResolver;

    protected HeaderCustomizer headerCustomizer;

    public HeaderAnnotationModelWriter(Class<D> modelClazz,  CellWriter cellWriter) {
        this(modelClazz, cellWriter, null);
    }

    public HeaderAnnotationModelWriter(Class<D> modelClazz,  CellWriter cellWriter, HeaderCustomizer headerCustomizer) {
        this.setModelClass(modelClazz);
        this.headerCustomizer = headerCustomizer;
        this.setCellWriter(cellWriter);
        this.headerResolver = new AnnotationModelHeaderResolver(modelClazz);

        {
            XSSFCellStyle cellStyle = cellWriter.createBasicStyle();
            cellStyle.getFont().setBold(true);

            cellStyle.setShrinkToFit(false);

            this.setHeaderCellStyle(cellStyle);
        }

    }

    
    @Override
    public List<? extends PositionCell> resolveHeaderCells() {
        List<PositionCell> result = new ArrayList<>();

        // 扩展层级
        Map<HeaderCell, List<List<HeaderCustomInfo>>> headerExtMap = new LinkedHashMap<>();
        for(HeaderCell hc : headerResolver.getFlatHeaders()) {
            if(headerCustomizer == null) {
				headerExtMap.put(hc, Arrays.asList(Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc))));
            }else {
                headerExtMap.put(hc, headerCustomizer.customHeader(hc));
            }
        }
        int maxLevel = headerExtMap.values().stream()
                .map(squareHeaders -> squareHeaders.size())
                .max((i1, i2) -> i1 - i2).orElse(1);


        headerExtMap.forEach((hc, squareHeaders) -> {
        	int maxWidth = squareHeaders.get(squareHeaders.size() - 1).size();
        	int curCol = 0;
        	for(int colIndex = 0; colIndex < maxWidth; colIndex++) {// 从左往右处理, 按列切割成层次标题列表
        		int curColIndex = colIndex;
        		List<HeaderCustomInfo> colLevelHeaders = squareHeaders.stream()
        				.map(levelHeaders -> levelHeaders != null  && curColIndex < levelHeaders.size() ? levelHeaders.get(curColIndex) : EMPTYCELL)
        				.collect(Collectors.toList());
        		
        		processColHeaderLevels(result, maxLevel, hc, curCol, colLevelHeaders);
        		
        		HeaderCustomInfo curBottomHeader = squareHeaders.get(squareHeaders.size() - 1).get(curCol);
        		curCol += curBottomHeader.getXspan();
        	}
        });
        

        return result;        
    }

	private void processColHeaderLevels(List<PositionCell> result, int maxLevel, HeaderCell hc, int curCol,
			List<HeaderCustomInfo> colLevelHeaders) {
		
		int curRow = 0;
		// 从上往下依次写入层次列头
		for(HeaderCustomInfo levelHeader : colLevelHeaders) {
			if(levelHeader == null || levelHeader == EMPTYCELL) {
				curRow++;
				continue;
			}

			PositionCell pc;
			if(curRow == 0) {
				int yspan = maxLevel - colLevelHeaders.size() + 1;
				PositionCell pc0 = new PositionCell(curRow, hc.getCol() + curCol , levelHeader.getVal(), levelHeader.getXspan(), yspan, hc.getWidth());
				while(curRow < maxLevel) {
					if(intersects(pc0, result)) {
						curRow++;  
						pc0.setRow(curRow);
						pc0.setYspan(pc0.getYspan() - 1);
					}else {
						curRow += pc0.getYspan();
						break;
					}

				}
				pc = pc0;
			}else {
				pc = new PositionCell(curRow, hc.getCol() + curCol, levelHeader.getVal(), levelHeader.getXspan(), 1, hc.getWidth());
				curRow += pc.getYspan();
			}

			pc.setCellStyleCustomizer(hc.getHeaderCellStyleCustomizer());

			result.add(pc);

		}
		
	}


    private boolean intersects(PositionCell pc0, List<PositionCell> result) {
    	for(PositionCell pc : result) {
    		Rectangle intersec = pc.toRectangle().intersection(pc0.toRectangle());
    		if(!intersec.isEmpty()) {
    			return true;
    		}
    	}
		return result.stream().anyMatch(pc -> pc.toRectangle().intersects(pc0.toRectangle()));
	}

	@Override
    public List<ValCell> modelToValCells(D model) {
        List<ValCell> valCells = new ArrayList<>();        
        modelToValCells(model, headerResolver.getHierarchyHeaders(), 0, valCells);
        return valCells;
    }

    @Override
    public Point writeModels(Iterable<D> models, boolean writeHeader) {
        return super.writeModels(models, writeHeader);
    }


    /**
     * 把模型映射为excel单元格模型列表。返回模型占用的行数
     * @param vo
     * @param headers
     * @param curRow
     * @param result
     * @return
     */
    private int modelToValCells(Object vo, List<HeaderCell> headers, int curRow,  List<ValCell> resultValCells) {
        int result = 1;

        List<ValCell> primitiveCells = new ArrayList<>();
        for(HeaderCell hc : headers) {
            Object cellval = getCellVal(vo, hc);

            // c1:递归获取模型对应的单元格
            if(hc.hasSub() && cellval != null) { 
                Collection<Object> subModels = expandToCollection(cellval);

                // 递归
                int subCurRow = curRow;
                for(Object sub : subModels) {
                    subCurRow += modelToValCells(sub, hc.getSubHeaders(), subCurRow, resultValCells);
                }
                result = Math.max(result, subCurRow - curRow);

            }else if(hc.getSubModelClazz() == null){ 
                Collection<Object> subModels = expandToCollection(cellval);
                
                // c2: 通过转换器把模型转换成cell列表
                List<PositionCell> modelCells = subModelsToCells(hc, subModels);
                Point maxXy = getMaxColRow(modelCells);
                
                for(PositionCell pcell : modelCells) {
                    ValCell vcell = new ValCell(hc, curRow + pcell.getRow(), pcell.getYspan(), pcell.getVal());
                    vcell.setCol(pcell.getCol());
                    vcell.setXspan(pcell.getXspan());
                    vcell.setXwidth(pcell.getXwidth());
                    vcell.setCellStyleCustomizer(pcell.getCellStyleCustomizer() != null 
                            ? pcell.getCellStyleCustomizer() : hc.getValueCellStyleCustomizer());
                    
                    resultValCells.add(vcell);
                    if(maxXy.y <= 1) {
                        primitiveCells.add(vcell);
                    }
                }
                result = Math.max(result, maxXy.y);
            }
        }

        for(ValCell priCell : primitiveCells) { // 行合并设置
            priCell.setYspan(result);
        }

        return result;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
	private List<PositionCell> subModelsToCells(HeaderCell hc, Collection<Object> subModels) {
        Point curPos = new Point(0, 0);
        List<PositionCell> result = new ArrayList<>();
        for(Object model : subModels) {
        	ModelToCellsConverter converter = hc.getModelToCellsConverter() == null ? DefaultModelToCellsConverter.getInstance() : hc.getModelToCellsConverter();
            List<? extends PositionCell> cells = converter.modelToValCells(model);
            
            for(PositionCell cell : cells) {
                cell.setCol(curPos.x + cell.getCol());
                cell.setRow(curPos.y + cell.getRow());
                if(cells.size() == 1 && !supportWriteHorizontal()) { // converter只输出唯一的单元格，则其横向合并单元格由列头决定
                	cell.setXspan(hc.getXspan());
                }
                
                result.add(cell);
            }
            // 重新计算当前位置
            Point size = getMaxColRow(cells);
            if(hc.isModelToCellsHorizontal() && supportWriteHorizontal()) { // 水平扩展
                curPos.y = 0;
                curPos.x = size.x;
            }else { // 垂直扩展
                curPos.y = size.y;
                curPos.x = 0;
            }      
        }
        return result;
    }

    /**
     * 是否支持水平写入数据模型。默认不支持。
     * 子类若要支持此特性，需要根据正文内如动态计算列头的合并单元格数。可参考 {@link DynaHeaderAnnotationModelWriter}实现
     * @return
     */
    public boolean supportWriteHorizontal() {
		return false;
	}

	@SuppressWarnings("unchecked")
    private Collection<Object> expandToCollection(Object cellval) {
        Collection<Object> subModels = new ArrayList<>();
        if(cellval instanceof Collection) {
            subModels = (Collection<Object>) cellval;
        }else if(cellval != null && cellval.getClass().isArray()) {
            for(int i = 0; i < Array.getLength(cellval); i++) {
                subModels.add(Array.get(cellval, i));
            }
        }else {
            subModels.add(cellval);
        }
        return subModels;
    }

    private Object getCellVal(Object vo, HeaderCell hc) {
        if(hc.getModelClazz().isAssignableFrom(vo.getClass())) {
            try {
            	return BeanUtil.getProperty(vo, hc.getPropertyName());
            } catch (Exception e) {
                log.info("获取模型对象值出错：{}, {}, {}", hc.getPropertyName(), vo, e);
            }
        }
        return null;
    }

    @Override
    protected void writeCellValue(int baseRow, int baseCol, PositionCell vc) {
        if(vc instanceof ValCell) {
            ValCell valCell = (ValCell) vc;
            writeCellValue(baseRow + vc.getRow(), baseCol + valCell.getHeader().getCol() + vc.getCol(),
                    vc.getVal(), new Point(vc.getXspan(), vc.getYspan()), vc.getCellStyleCustomizer(), false);
        }else {
            super.writeCellValue(baseRow, baseCol, vc);
        }
       
    }
    

    /**
     * excel格子的值模型
     * @author mengxianming@2021-8-19 17:39:11
     *
     */
    @Data
    @NoArgsConstructor
    public static class ValCell extends PositionCell{
        private HeaderCell header;
        
        public ValCell(HeaderCell header, int row, int yspan, Object val) {
            super(row, 0, val, header.getXspan(), yspan, 15);
            this.header = header;
        }
    }
}